/*
    3D Office Environment
    Copyright (C) 2011  Till Merker

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include <Ogre.h>
#include <OgreTexture.h>
#include <RenderSystems/GL/OgreGLTexture.h>
#include <RenderSystems/GL/OgreGLPlugin.h>

#include "OfficeEnv.h"
#include "globals.h"
#include "BSPOfficeScene.h"
#include "DotSceneOfficeScene.h"

using namespace Ogre;
  
namespace OfficeEnv {

OfficeEnv::OfficeEnv() : mScene(0) {
    initGLfuncs();    
}
    
OfficeEnv::~OfficeEnv() {
    shutdown();
}

void OfficeEnv::startup(int width, int height, int numDesktops, char *office) {

    /* Save GL state including all attrib bits before startup sequence */
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    saveGLState();
    
    /* Initialize OGRE */
    mRoot = new Root();
	
    /* Initialize resource manager for the specfific scene  */
    initResources(office);
    ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
        
    /* install and initialize GL system */
    mRoot->installPlugin(new GLPlugin());    	
    RenderSystem *rs = mRoot->getRenderSystemByName("OpenGL Rendering Subsystem");
    mRoot->setRenderSystem(rs);
    mRoot->initialise(false);
  
    /* Create render window. Set options to tell OGRe to use the current context instead of setting up a new one */
    NameValuePairList misc;
    misc["currentGLContext"] = String("true"); 
    misc["externalGLControl"] = String("true");
    mWindow = mRoot->createRenderWindow("Main RenderWindow", width, height, false, &misc);

    /* Initialize RTT textures for the desktops */
    initTextures(width, height, numDesktops);
    
    /* Load office scene, using the correct scene manager depending on filename */
    if(ResourceGroupManager::getSingleton().resourceExists("General", "office.bsp")) {
	mScene = new BSPOfficeScene();
	mScene->init(mRoot, width, height, numDesktops, "office.bsp");
    } else {
	mScene = new DotSceneOfficeScene();
	mScene->init(mRoot, width, height, numDesktops, "office.scene");
    }
        
    /* Create the target texture to render to */
    TexturePtr targetTex = TextureManager::getSingleton().createManual("TargetTexture",
	ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, width, height, 0, PF_R8G8B8, TU_RENDERTARGET);     
    mRenderTexture = targetTex->getBuffer()->getRenderTarget();
    
    /* Set up viewport */
    mViewport = mRenderTexture->addViewport(mScene->getCamera());
    mViewport->setAutoUpdated(true);
    mTargetTexture = (Ogre::GLTexture*)targetTex.getPointer();
     
    /* HACK    
     * Ogre doesn't activate the render target in the RenderSystem if it doesn't change
     */    
    mViewport->setDimensions(mViewport->getLeft(), mViewport->getTop(), mViewport->getWidth(), mViewport->getHeight());
    mRenderTexture->update();
    
    /* Restore GL state */
    restoreGLState();
    glPopAttrib();
}
   
void OfficeEnv::shutdown() {
    if(mRoot) {
	
	/* OGRE sets GLContext/Drawable to Null on shutdown, so remember them and set back afterwards */
	GLXDrawable oldDrawable = glXGetCurrentDrawable();
	GLXContext  oldContext  = glXGetCurrentContext();
	Display    *oldDisplay  = glXGetCurrentDisplay();	
				
	/* shut down the scene and OGRE */
	delete mScene;
	delete mRoot;
	mRoot = NULL;

	glXMakeCurrent (oldDisplay, oldDrawable, oldContext);
			
    }
}
  
void OfficeEnv::initResources(char *office) {
    /* Remove any previously added resource locations */
    Ogre::StringVectorPtr locations = ResourceGroupManager::getSingleton().listResourceLocations(ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
    ResourceGroupManager::getSingleton().shutdownAll();
    for(unsigned int i = 0; i < locations->size(); i++) 
	ResourceGroupManager::getSingleton().removeResourceLocation(locations->at(i));
    
    /* Add possible resource locations for common resources */
    ResourceGroupManager::getSingleton().addResourceLocation(String(INSTALL_PREFIX) + "/share/officeenv", "FileSystem");
    ResourceGroupManager::getSingleton().addResourceLocation(String(getenv("HOME")) + "./officeenv", "FileSystem");
    ResourceGroupManager::getSingleton().addResourceLocation("/usr/local/share/officeenv", "FileSystem");
    ResourceGroupManager::getSingleton().addResourceLocation("/usr/share/officeenv", "FileSystem");    

    /* Add possible resource locations for this specific scene */
    ResourceGroupManager::getSingleton().addResourceLocation(String(INSTALL_PREFIX) + "/share/officeenv/" + office, "FileSystem");
    ResourceGroupManager::getSingleton().addResourceLocation(String(getenv("HOME")) + "./officeenv/" + office, "FileSystem");
    ResourceGroupManager::getSingleton().addResourceLocation(String("/usr/local/share/officeenv/") + office, "FileSystem");
    ResourceGroupManager::getSingleton().addResourceLocation(String("/usr/share/officeenv/") + office, "FileSystem");    
}
  
void OfficeEnv::initTextures(int width, int height, int numDesktops) {
    /* Create RTT textures and materials for all desktops */
    for(int i = 0; i < numDesktops; i++) {	
	char texName[16];
	char matName[16];
	sprintf((char*)&texName, "DeskTex%i", i);
	sprintf((char*)&matName, "DeskMat%i", i);	
	
	TexturePtr texture = TextureManager::getSingleton().createManual(texName,
	    ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, width, height, 0, PF_R8G8B8, TU_RENDERTARGET);
	mDesktopTexture[i] = (GLTexture*) texture.getPointer();      
	MaterialPtr material = MaterialManager::getSingleton().create(matName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
	material->getTechnique(0)->getPass(0)->createTextureUnitState(texName); 	
	material->getTechnique(0)->setCullingMode(CULL_NONE);
	material->setLightingEnabled(false);
	
	/* Setup alpha blending to make desktops translucent */
	material->setSceneBlending(SBT_TRANSPARENT_ALPHA);
	material->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setAlphaOperation(
	    LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, 0.7);    	
    }

}

void OfficeEnv::renderFrame() {
    saveGLState();
 
    /* advance the current animation, if any */
    mScene->updateAnimation();
 
    /* HACK    
     * Ogre doesn't activate the render target in the RenderSystem if it doesn't change
     */    
    mViewport->setDimensions(mViewport->getLeft(), mViewport->getTop(), mViewport->getWidth(), mViewport->getHeight());
    
    /* render the frame */
    mRenderTexture->update();

    restoreGLState();
}

void OfficeEnv::saveGLState() {
    glPushAttrib(GL_ENABLE_BIT);
    
    glGetFloatv(GL_MODELVIEW_MATRIX, &mModelviewMatrix[0]);
    glGetFloatv(GL_PROJECTION_MATRIX, &mProjectionMatrix[0]);
    glGetFloatv(GL_TEXTURE_MATRIX, &mTextureMatrix[0]);
    
    glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &mFramebuffer);
    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &mArrayBuffer);
    glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &mElementArrayBuffer);
    
    glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &mTextureEnvMode);
}

void OfficeEnv::restoreGLState() {
    glPopAttrib(); 
    
    glMatrixMode(GL_TEXTURE);
    glLoadMatrixf(&mTextureMatrix[0]);
    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(&mProjectionMatrix[0]);    
    glMatrixMode(GL_MODELVIEW);    
    glLoadMatrixf(&mModelviewMatrix[0]);
    
    OE_glBindFramebuffer(GL_FRAMEBUFFER_EXT, mFramebuffer);
    glBindBufferARB(GL_ARRAY_BUFFER_ARB, mArrayBuffer);
    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, mElementArrayBuffer);
    
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, mTextureEnvMode);
}
    
GLuint OfficeEnv::getTexture(int desktop) {
    return mDesktopTexture[desktop]->getGLID();
}

GLuint OfficeEnv::getTargetTexture() {
    return mTargetTexture->getGLID();
}

int OfficeEnv::getTargetedDesktop() {
    return mScene->getTargetedDesktop();
}

CameraAnimation OfficeEnv::getCurrentAnimation() {
    return mScene->getCurrentAnimation();
}

void OfficeEnv::inputAction(int forward, int sidestep, float yaw, float pitch) {    
    mScene->inputAction(forward, sidestep, yaw, pitch);
}

void OfficeEnv::startAnimation(CameraAnimation animation, int param) {
    mScene->startAnimation(animation, param);
}

}
